package pacBioSimple;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Random;


import tools.kmer.KmerSet_binary;
import tools.kmer.kmerFunctions;
import tools.sequences.sequenceUtils;

public class PacBioSeedBridgingKmer {
	protected StringBuilder seed1 = null;
	protected StringBuilder seed2= null;
	protected int distance=0;
	protected List<StringBuilder> candidate = null;
	protected List<StringBuilder> repeatsStoppPath = null;
	protected PacBioNodeStack<Integer> stack=null;
	protected CodingTable codingTable=null;
	protected KmerSet_binary ks=null;
	protected boolean dir=false; //false ende // true anfang
	protected int id;
	protected int repVal=0;
	//private boolean stopp=false;
	protected Path candidatePath = null;
	protected KKmerList kmerVector=null;
	protected StringBuilder readSequence =null;
	protected int seed1Pos;
	protected int seed2Pos;
	protected Random generator = new Random();

	
	protected PacBioSeedBridgingKmer(StringBuilder seed_1, StringBuilder seed_2, int seed1Pos, int seed2Pos, int distance, CodingTable table, KmerSet_binary ks, boolean dir, int repVal, KKmerList kmerVector, StringBuilder readSeq, Path candidatePath)
	{
		this.distance=distance+distance*20/100;
		this.codingTable=table;
		this.ks=ks;
		this.candidatePath = candidatePath;
		this.dir=dir;
		this.repVal=repVal;
		this.readSequence=readSeq;
		StringBuilder seed1 = new StringBuilder(seed_1);
		StringBuilder seed2 = new StringBuilder(seed_2);
		this.seed1Pos=seed1Pos;
		this.seed2Pos=seed2Pos;
		//temp = new PacBioSeedBridgingReduceExtend(seeds.get(i+1), seeds.get(i), distance, table, ks,true,repVal,kmerVector,readSequence, newReversedPath);
		//System.out.println("seed1: "+ seed1+ " seed2: "+seed2);
		if(dir)
		{
			seed2=sequenceUtils.reverseComplement2(seed2);
			seed1=sequenceUtils.reverseComplement2(seed1);
		}
		//System.out.println("seed1: "+ seed1+ " seed2: "+seed2);
		this.seed1 = seed1;
		this.seed2 = seed2;
		this.kmerVector=kmerVector;
		init();
	}
	
	
	public PacBioSeedBridgingKmer(int seed1Pos, int seed2Pos, int distance,CodingTable table, KmerSet_binary ks,boolean dir,int repVal,KKmerList kmerVector,StringBuilder readSeq,Path candidatePath )
	{
		this.distance=distance+distance*40/100;
		this.codingTable=table;
		this.ks=ks;
		this.candidatePath = candidatePath;
		this.dir=dir;
		this.repVal=repVal;
		this.readSequence=readSeq;
		StringBuilder seed1 =null;
		StringBuilder seed2 =null;
		seed1= new StringBuilder(readSeq.substring(seed1Pos, seed1Pos+ks.getKmerSize()));
		seed2= new StringBuilder(readSeq.substring(seed2Pos, seed2Pos+ks.getKmerSize()));
		this.seed1Pos=seed1Pos;
		this.seed2Pos=seed2Pos;
		//temp = new PacBioSeedBridgingReduceExtend(seeds.get(i+1), seeds.get(i), distance, table, ks,true,repVal,kmerVector,readSequence, newReversedPath);
		//System.out.println("seed1: "+ seed1+ " seed2: "+seed2);
		if(dir)
		{
			seed2=sequenceUtils.reverseComplement2(seed2);
			seed1=sequenceUtils.reverseComplement2(seed1);
		}
		//System.out.println("seed1: "+ seed1+ " seed2: "+seed2);
		this.seed1 = seed1;
		this.seed2 = seed2;
		this.kmerVector=kmerVector;
		init();
	}
	
	public PacBioSeedBridgingKmer(int seed1Pos,int distance,CodingTable table, KmerSet_binary ks,boolean dir,int repVal,KKmerList kmerVector,StringBuilder readSeq,Path candidatePath)
	{
		StringBuilder seed1 =null;
		
		seed1= new StringBuilder(readSeq.substring(seed1Pos, seed1Pos+ks.getKmerSize()));
		this.seed1Pos=seed1Pos;
		if(dir)
		{
			seed1=sequenceUtils.reverseComplement2(seed1);
		}
		this.seed1=seed1;
		this.distance=distance+distance*20/100;
		this.codingTable=table;
		this.ks=ks;
		this.candidatePath = candidatePath;
		this.dir=dir;
		this.repVal=repVal;
		this.readSequence=readSeq;
		this.kmerVector=kmerVector;
		init();
	}
	
	private void init()
	{
		stack = new PacBioNodeStack<Integer>();
		this.candidate=new ArrayList<StringBuilder>();
		this.repeatsStoppPath = new ArrayList<StringBuilder>();
		
	}
	
	protected int checkKmerDistribution(String kmer)
	{
		//System.out.print(kmer+ " kmer check\n");
		if(kmer.equals(""))
		{
			return 0;
		}
		String kmerToUse = kmerFunctions.kmerToUse(kmer);
		BitSet bS = ks.getUtils().stringToBitSet(kmerToUse);
		//BitSet bS = ks.getUtils().stringToBitSet(kmerFunctions.kmerToUse(kmer.toString()));
		if(ks.exists(kmerToUse))
		{
			return(ks.getNumber(bS));
		}
		else
		{
			return 0;
		}
	}
	
	protected void addCandidate(StringBuilder kmer)
	{
		if(dir)
		{
			this.candidate.add(sequenceUtils.reverseComplement2(kmer));
		}
		else
		{
			this.candidate.add(kmer);
		}
	}
	
	protected StringBuilder pop(StringBuilder kmer, boolean mark)
	{
		if(!stack.empty() && mark)
		{
			addCandidate(new StringBuilder(kmer));
//			if(this.candidate.size()>=11)
//			{
//				this.stopp=true;
//			}
		}
		else if(!stack.empty() && stack.getLength() >= this.distance/2 || (!stack.empty() && seed2==null))
		{
			setRepatPath(kmer);
		}
		stack.pop();
		--id;
		kmer.deleteCharAt(kmer.length()-1);
		System.out.println(stack.getLength());
		if(!stack.empty())
		{
			stack.setPeek(stack.peek()+1);  
		}
		return kmer;
	}
	
	
	public void run()
	{
		id=0;
		//here we have to fill all the nasty things building up the stack, saving the result
		//first add the first root node
		if(stack.empty())
		{
			stack.push(0);
			++id;
		}
		//System.out.println("seed1: " + this.seed1+ " seed2: "+ this.seed2);

		StringBuilder kmer=new StringBuilder(this.seed1);	
		while(!stack.empty())
		{		
//			++counter;
//			log.log(Level.WARNING,"StackDeep: "+stack.getLength() + "StackNode: "+stack.peek().getId()+" "+stack.peek().getStatus() +" Distance "+ distance+"\n");
			//if(counter % 100==0)
			System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
			System.out.println("kmer "+kmer);
			if(stack.peek()>=4)
			{
				kmer=pop(kmer,false);
			}
			else if(stack.getLength() > this.distance)
			{
				if(seed2!=null)
				{
					kmer=pop(kmer,false);
				}
				else
				{
					kmer=pop(kmer,true);
				}	
			}
			else if(seed2!=null && kmer.length()>ks.getKmerSize() && kmer.indexOf(seed2.toString())!=-1)
			{
				kmer=pop(kmer,true);
			}
			else
			{
				//check last element
				//building up the kmer
				StringBuilder tempNewKmer = new StringBuilder(kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length()));
				tempNewKmer.append(codingTable.getCodingTableEntry(stack.peek()));
				if(kmer.indexOf(tempNewKmer.toString()) !=-1)
				{
					System.out.println("double kmer" + tempNewKmer);
					stack.setPeek(stack.peek()+1);
					setRepatPath(kmer);
				}
				else
				{					
					kmer.append(codingTable.getCodingTableEntry(stack.peek()));
					//if(counter % 100==0)
					System.out.println("Path "+kmer.toString());
					//check Kmer
					//int a=kmer.length();
					//int b = ks.getKmerSize();
					//String test = kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length());
					
					int kmerVal = checkKmerDistribution(kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length()));
	//				if(kmerVal!=0)
	//				{
	//					System.out.println("Kmer "+kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length())+" "+kmerVal);
	//					System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
	//				}
					if(kmerVal!=0 && kmerVal < repVal)
					{
						stack.push(0);
						++id;
					}					
					else if(kmerVal!=0 && kmerVal < repVal)
					{
						stack.push(0);
						++id;
					}
					else if(kmerVal>=repVal)
					{
						System.out.println("expanding kmer ");
						System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
						System.out.println("kmer "+kmer);
						System.out.println(kmerVector.getKmerSize());
						System.out.println("start");
						try
						{
							kmer=expandRepKmer(kmer);
						}
						catch(Exception e)
						{
							e.printStackTrace();
						}
						System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
						System.out.println("kmer "+kmer);


					}
					else
					{
						kmer.deleteCharAt(kmer.length()-1);
						stack.setPeek(stack.peek()+1);
					}
				}
			}
		}	
	}
	
	
	protected StringBuilder expandRepKmer(StringBuilder kmer)
	{
		System.out.println("argagegagagagagga");
		System.out.println("extenstion size "+ kmerVector.getKmerSize() );
		System.out.println("error?");
		System.out.println(kmerVector.toString());
		//sequenziell later maybe parallel
		List<Integer>candidatePath=new ArrayList<Integer>();
		List<Double>alignmentResult = new ArrayList<Double>();
		for(int i=0; i < kmerVector.getkKmerList().size();++i)
		{
			System.out.println("kmer phase "+i);
			StringBuilder kmerCopy = new StringBuilder(kmer);
			kmerCopy.append(kmerVector.getkKmerList().get(i));
			int kmerVal = checkKmerDistribution(kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length()));
			if(kmerVal!=0)
			{
				//do alignment save result#
				StringBuilder template = null;
				if(dir)
				{
					int begin=seed1Pos+ks.getKmerSize();
					if(begin-kmerCopy.length()<0)
					{
						begin=0;
					}
					else
					{
						begin=begin-kmerCopy.length();
					}
					template=new StringBuilder(readSequence.substring(begin, seed1Pos+ks.getKmerSize()));
					template = sequenceUtils.reverseComplement2(template);
				}
				else
				{
					int end = seed1Pos;
					if(end+kmerCopy.length()>=readSequence.length())
					{
						end=readSequence.length();
					}
					else
					{
						end = end+kmerCopy.length();
					}
					template = new StringBuilder(readSequence.substring(seed1Pos, end));
				}
				
				
				//do alignment
				
				AlignmentObject tempWorker = new AlignmentObject();
				tempWorker.setTemplate(template.toString());
				tempWorker.setA(kmerCopy.toString());
				tempWorker.setGap(2);
				tempWorker.setEx(1);
				tempWorker.setFlag(true);
				ThreadAlignmentTraceback worker = new ThreadAlignmentTraceback(tempWorker);
				
				System.out.println("alignment kmer ex "+ tempWorker.toString());
				
				AlignmentObject temp = worker.runIt();
				
				System.out.println("alignment kmer ex out"+ temp.toString());

				if(alignmentResult.size()==0)
				{
					alignmentResult.add(temp.getScore());
					candidatePath.add(i);
				}
				else
				{
					if(temp.getScore()>=alignmentResult.get(alignmentResult.size()-1))
					{
						if(temp.getScore()>alignmentResult.get(alignmentResult.size()-1))
						{
							alignmentResult.clear();
							alignmentResult.add(temp.getScore());
							candidatePath.clear();
							candidatePath.add(i);
						}
						else
						{
							alignmentResult.add(temp.getScore());
							candidatePath.add(i);
						}
					}
				}
			}			
		}
		int choice;
		if(candidatePath.size()>1)
		{
			choice= candidatePath.get(generator.nextInt(candidatePath.size()));
		}
		else
		{
			choice=candidatePath.get(0);
		}
		//update the stack
		StringBuilder sbChoice = new StringBuilder(kmerVector.getkKmerList().get(choice));
		for(int i=0; i<kmerVector.getKmerSize();++i)
		{
			stack.push(4);
			/*
			if(sbChoice.charAt(i)=='A')
			{
				stack.push(0);
			}
			else if(sbChoice.charAt(i)=='C')
			{
				stack.push(1);
			}
			else if(sbChoice.charAt(i)=='G')
			{
				stack.push(2);
			}
			else if(sbChoice.charAt(i)=='T')
			{
				stack.push(3);
			}
			*/
		}
		System.out.println(sbChoice.toString());
		kmer.append(sbChoice);
		//now we have to select the one, update the stack and return new kmer
		
		return kmer;
	}
	
	
	public void run2()
	{
		id=0;
		//here we have to fill all the nasty things building up the stack, saving the result
		//first add the first root node
		if(stack.empty())
		{
			stack.push(0);
			++id;
		}

		StringBuilder kmer=new StringBuilder(this.seed1);	
		int counter =0;
		while(!stack.empty())
		{		
			++counter;
//			log.log(Level.WARNING,"StackDeep: "+stack.getLength() + "StackNode: "+stack.peek().getId()+" "+stack.peek().getStatus() +" Distance "+ distance+"\n");
			//if(counter % 100==0)
			System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
			System.out.println("kmer "+kmer);
			if(stack.peek()>=4)
			{
				kmer=pop(kmer,false);
			}
			else if(stack.getLength() > this.distance)
			{
				if(seed2!=null)
				{
					kmer=pop(kmer,false);
				}
				else
				{
					kmer=pop(kmer,true);
				}	
			}
			else if(seed2!=null && kmer.length()>ks.getKmerSize() && kmer.indexOf(seed2.toString())!=-1)
			{
				kmer=pop(kmer,true);
			}
			else
			{
				//check last element
				//building up the kmer
				StringBuilder tempNewKmer = new StringBuilder(kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length()));
				tempNewKmer.append(codingTable.getCodingTableEntry(stack.peek()));
				if(kmer.indexOf(tempNewKmer.toString()) !=-1)
				{
					System.out.println("double kmer" + tempNewKmer);
					stack.setPeek(stack.peek()+1);
					setRepatPath(kmer);
				}
				else
				{					
					kmer.append(codingTable.getCodingTableEntry(stack.peek()));
					//if(counter % 100==0)
					System.out.println("Path "+kmer.toString());
					//check Kmer
					//int a=kmer.length();
					//int b = ks.getKmerSize();
					//String test = kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length());
					
					int kmerVal = checkKmerDistribution(kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length()));
	//				if(kmerVal!=0)
	//				{
	//					System.out.println("Kmer "+kmer.substring(kmer.length()-ks.getKmerSize(), kmer.length())+" "+kmerVal);
	//					System.out.println("StackDeep: "+stack.getLength() + " " +stack.peek() +" Distance "+ distance +"\n");
	//				}
					if(kmerVal!=0 && kmerVal < repVal)
					{
						stack.push(0);
						++id;
					}
					else
					{
						if(kmerVal >=repVal)
						{
							//write log
							setRepatPath(kmer);
							System.out.println("stopped by repKmer");
						}
						kmer.deleteCharAt(kmer.length()-1);
						stack.setPeek(stack.peek()+1);
					}
				}
			}
		}
		if(this.candidate.size()==0)
		{
			this.candidate.add(new StringBuilder(""));
		}
		setPaths();
	}
	
	
//	public List<StringBuilder> getCandidates()
//	{
//		return this.candidate;
//	}

	
	protected void setRepatPath(StringBuilder kmer)
	{
		if(dir)
		{
			//System.out.println("fragement reveerse ");
			//System.out.println(sequenceUtils.reverseComplement2(kmer));
			if(this.repeatsStoppPath.size()==0)
			{
				this.repeatsStoppPath.add(new StringBuilder(sequenceUtils.reverseComplement2(kmer)));
			}
			else if(!this.repeatsStoppPath.get(this.repeatsStoppPath.size()-1).equals(sequenceUtils.reverseComplement2(kmer)))
			{						
				this.repeatsStoppPath.add(new StringBuilder(sequenceUtils.reverseComplement2(kmer)));
			}
			
		}
		else
		{
			
			//System.out.println("fragment "+kmer);
			if(this.repeatsStoppPath.size()==0)
			{
				this.repeatsStoppPath.add(new StringBuilder(kmer));
			}
			else if(this.repeatsStoppPath.size()>0&&!this.repeatsStoppPath.get(this.repeatsStoppPath.size()-1).equals(kmer))
			{
				this.repeatsStoppPath.add(new StringBuilder(kmer));
			}
		}
		//System.out.println("done");
	}
	protected void setPaths_save()
	{
		Collections.sort(this.candidate,new StringLengthComperator());
		
		if(this.candidate.size()!=0 && this.candidate.get(0).length()>this.seed1.length())
		{
			this.repeatsStoppPath.clear();
		}
		else
		{
			Collections.sort(this.repeatsStoppPath,new StringLengthComperator());
		
			if(seed2==null && !this.repeatsStoppPath.isEmpty())
			{
				//get the longest ones,	
				int max_length=0;
				int counter=0;
				List<StringBuilder> temp = new ArrayList<StringBuilder>();
				while(counter < this.repeatsStoppPath.size())
				{
					if(max_length==this.repeatsStoppPath.get(counter).length())
					{
						temp.add(new StringBuilder(this.repeatsStoppPath.get(counter)));
					}
					else if(max_length>this.repeatsStoppPath.get(counter).length())
					{
						temp.clear();
						temp.add(new StringBuilder(this.repeatsStoppPath.get(counter)));
						max_length=this.repeatsStoppPath.get(counter).length();
					}
					++counter;
				}
				this.repeatsStoppPath=temp;
			}
		}		
		this.candidatePath.setFullpaths(this.candidate);
		this.candidatePath.setRepeatsEndedpaths(this.repeatsStoppPath);
	}
	
	protected void setPaths()
	{
		Collections.sort(this.candidate,new StringLengthComperator());
		
		if(this.candidate.size()!=0 && this.candidate.get(0).length()>this.seed1.length())
		{
			this.repeatsStoppPath.clear();
		}
		else
		{
			Collections.sort(this.repeatsStoppPath,new StringLengthComperator());
		
			if(!this.repeatsStoppPath.isEmpty())
			{
				//get the longest ones,	
				int max_length=0;
				int counter=0;
				List<StringBuilder> temp = new ArrayList<StringBuilder>();
				while(counter < this.repeatsStoppPath.size())
				{
					if(max_length==this.repeatsStoppPath.get(counter).length())
					{
						temp.add(new StringBuilder(this.repeatsStoppPath.get(counter)));
					}
					else if(max_length>this.repeatsStoppPath.get(counter).length())
					{
						temp.clear();
						temp.add(new StringBuilder(this.repeatsStoppPath.get(counter)));
						max_length=this.repeatsStoppPath.get(counter).length();
					}
					++counter;
				}
				this.repeatsStoppPath=temp;
			}
		}		
		this.candidatePath.setFullpaths(this.candidate);
		this.candidatePath.setRepeatsEndedpaths(this.repeatsStoppPath);
	}
	
	
	protected void checkAlternativePathLength()
	{
		
		if(this.repeatsStoppPath.size()>0)
		{	
			List<StringBuilder> mem = new ArrayList<StringBuilder>();
			mem.add(this.repeatsStoppPath.get(0));
			for(int i=1; i < repeatsStoppPath.size();++i)
			{
				if(repeatsStoppPath.get(i).length()>ks.getKmerSize()+(ks.getKmerSize()*10/100))
				{
					mem.add(this.repeatsStoppPath.get(i));
				}
			}
			this.repeatsStoppPath=mem;
		}
	}
	
	public Path getPaths()
	{
		
		return this.candidatePath;
	}
	
	public Path call() throws Exception
	{
		return runIt();
	}
	
	public Path runIt()
	{
		run();
		return getPaths();
	}

}
